"""
================================================================================

    abc2esac - ABC to EsAC conversion
    Copyright (C) 2010, Marcus Weseloh

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

================================================================================
"""

from fractions import Fraction as F

from abcesac.parser import BASE_NOTES
from abcesac.music import Note, Tuplet, Rest

ESAC_HEADERS = (
    ('CUT', 'title'),
    ('REG', 'origin'),
)

class ESACFormatter(object):
    def __init__(self, collection, prefix='', index=0, grouper=None):
        self.collection = collection
        self.prefix = prefix
        self.counter = index
        self.shortest_length = None
        self.note_offset = 0
        self.bar_length = F(0)
        self.grouper = grouper
        self.note_count = 0
        self.phrase_endings = []
        self.new_phrase = False

    def to_string(self, song):
        try:
            self.note_count = 0
            self.counter += 1
            self.note_offset = BASE_NOTES.index(song.get_tonic()[0])
            self.shortest_length = song.get_shortest_length()
            headers = self.format_headers(song)

            if self.grouper:
                self.phrase_endings = self.grouper.group(song)

            notes = self.format_phrases(song)
            esac = ''
            if self.collection:
                esac += '%s\n' % self.collection
            if headers:
                esac += '%s\n' % (headers)
            esac += 'MEL[%s //]' % notes
            return esac
        except Exception, e:
            raise Exception('Error while parsing song "%s": %r' %
                            (song.title, e))

    def format_headers(self, song):
        out = []
        for key, attr in ESAC_HEADERS:
            val = getattr(song, attr, None)
            if val is not None:
                out.append('%s[%s]' % (key, val))
        out.append('KEY[%s%04d %02d %s %d/%d]' % (
            self.prefix, self.counter, self.shortest_length.denominator,
            song.get_tonic(), song.meter[0], song.meter[1]))
        return '\n'.join(out)

    def format_phrases(self, song):
        return self.format_bars(song, song.bars)

    def format_bars(self, song, bars):
        esac = []
        for bar_idx, bar in enumerate(bars):
            if bar_idx > 0 and (not isinstance(bar.elements[0], Rest) and
                                not self.new_phrase):
                esac.append('  ')
            esac.append(self.format_elements(song, bar.elements))
        return ''.join(esac)

    def format_elements(self, song, elements):
        esac = []
        for i, el in enumerate(elements):
            if isinstance(el, Note):
                if self.new_phrase:
                    self.new_phrase = False
                    esac.append('\n    ')
                esac.append(self.format_note(el))
                esac.append(self.format_length(el))
                if self.note_count+1 in self.phrase_endings:
                    self.new_phrase = True
                self.note_count += 1
            elif isinstance(el, Tuplet):
                esac.append(self.format_tuplet(song, el))
            elif isinstance(el, Rest):
                esac.append('0')
                esac.append(self.format_length(el))
            else:
                raise Exception('Element %s is of unknown type' % el)
        return ''.join(esac)

    def format_tuplet(self, song, tuplet):
        esac = '('
        esac += self.format_elements(song, tuplet.notes)
        esac += ')'
        return esac

    def format_note(self, note):
        esac = ''
        octave = note.octave
        note_idx = BASE_NOTES.index(note.name[0])
        final_idx = note_idx - self.note_offset
        if final_idx < 0:
            octave -= 1
        elif final_idx > 6:
            octave += 1

        if octave > 4:
            esac += '+' * (octave - 4)
        elif octave < 4:
            esac += '-' * abs(octave - 4)

        esac += str((final_idx % 7) + 1)
        esac += note.name[1:]
        return esac

    def format_length(self, note):
        esac = ''
        esac += ('_' * note.get_relative_length(self.shortest_length))
        esac += ('.' * note.dots)
        self.bar_length += note.get_total_length()
        return esac
